﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


using System;
using SlimDX.Multimedia;
using SlimDX.RawInput;
using Tesla.Input;
using Tesla.Windows.Util;

namespace Tesla.Windows.Input {
    /// <summary>
    /// Wrapper for using acquiring mouse data as Raw Input.
    /// </summary>
    public sealed class RawInputMouseWrapper : IMouseWrapper {
        private IntPtr _handle;
        private Util.Point _pos;
        private ButtonState _left;
        private ButtonState _right;
        private ButtonState _middle;
        private ButtonState _x1;
        private ButtonState _x2;
        private int _wheelDelta;

        /// <summary>
        /// Gets or sets the window handle the mouse is bound to. Mouse screen coordinates
        /// are relative to the top-left corner of this window.
        /// </summary>
        public IntPtr Handle {
            get {
                return _handle;
            }
            set {
                if(value != _handle) {
                    _handle = value;
                    Device.RegisterDevice(UsagePage.Generic, UsageId.Mouse, DeviceFlags.None, _handle);
                }
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RawInputMouseWrapper"/> class.
        /// </summary>
        public RawInputMouseWrapper() : this(IntPtr.Zero) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="RawInputMouseWrapper"/> class.
        /// </summary>
        /// <param name="handle">The handle.</param>
        public RawInputMouseWrapper(IntPtr handle) {
            Device.RegisterDevice(UsagePage.Generic, UsageId.Mouse, DeviceFlags.None, handle);
            Device.MouseInput += new EventHandler<MouseInputEventArgs>(MouseInput);
            _handle = handle;
            Util.Point pt;
            //Get the screen position of the mouse.
            NativeMethods.GetCursorPos(out pt);
            _pos = new Util.Point(pt.X, pt.Y);
            _left = ButtonState.Released;
            _right = ButtonState.Released;
            _middle = ButtonState.Released;
            _x1 = ButtonState.Released;
            _x2 = ButtonState.Released;
            _wheelDelta = 0;
        }

        private void MouseInput(Object sender, MouseInputEventArgs e) {
            MouseButtonFlags flags = e.ButtonFlags;

            if((flags & MouseButtonFlags.LeftDown) == MouseButtonFlags.LeftDown) {
                _left = ButtonState.Pressed;
            } else if((flags & MouseButtonFlags.LeftUp) == MouseButtonFlags.LeftUp){
                _left = ButtonState.Released;
            }

            if((flags & MouseButtonFlags.RightDown) == MouseButtonFlags.RightDown) {
                _right = ButtonState.Pressed;
            } else if((flags & MouseButtonFlags.RightUp) == MouseButtonFlags.RightUp) {
                _right = ButtonState.Released;
            }

            if((flags & MouseButtonFlags.MiddleDown) == MouseButtonFlags.MiddleDown) {
                _middle = ButtonState.Pressed;
            } else if((flags & MouseButtonFlags.MiddleUp) == MouseButtonFlags.MiddleUp) {
                _middle = ButtonState.Released;
            }

            if((flags & MouseButtonFlags.Button4Down) == MouseButtonFlags.Button4Down) {
                _x1 = ButtonState.Pressed;
            } else if((flags & MouseButtonFlags.Button4Up) == MouseButtonFlags.Button4Up) {
                _x1 = ButtonState.Released;
            }

            if((flags & MouseButtonFlags.Button5Down) == MouseButtonFlags.Button5Down) {
                _x2 = ButtonState.Pressed;
            } else if((flags & MouseButtonFlags.Button5Up) == MouseButtonFlags.Button5Up) {
                _x2 = ButtonState.Released;
            }

            _wheelDelta += e.WheelDelta;
            _pos.X += e.X;
            _pos.Y += e.Y;
        }

        /// <summary>
        /// Queries the current state of the mouse buttons and screen position.
        /// </summary>
        /// <returns>
        /// Current mouse state
        /// </returns>
        public MouseState GetMouseState() {
            //Convert to client coordinates
            Util.Point pt = new Util.Point(_pos);
            if(_handle != IntPtr.Zero) {
                NativeMethods.ScreenToClient(_handle, ref pt);
            }
            return new MouseState(pt.X, pt.Y, _wheelDelta, _left, _right, _middle, _x1, _x2);
        }

        /// <summary>
        /// Queries the current state of the mouse buttons and screen position,
        /// using the specified window handle.
        /// </summary>
        /// <param name="handle">Window handle</param>
        /// <returns>
        /// Current mouse state
        /// </returns>
        public MouseState GetMouseState(IntPtr handle) {
            //Current mouse position in screen coordinates
            Util.Point pt = new Util.Point(_pos);
            //Convert to client coordinates of the specified window handle.
            if(handle != IntPtr.Zero) {
                NativeMethods.ScreenToClient(handle, ref pt);
            }
            return new MouseState(pt.X, pt.Y, _wheelDelta, _left, _right, _middle, _x1, _x2);
        }

        /// <summary>
        /// Sets the position of the mouse relative to the top-left corner of the window the mouse
        /// wrapper is bound to.
        /// </summary>
        /// <param name="x">X position</param>
        /// <param name="y">Y position</param>
        public void SetPosition(int x, int y) {
            Util.Point pt;
            pt.X = x;
            pt.Y = y;
            if(_handle != IntPtr.Zero) {
                NativeMethods.ClientToScreen(_handle, ref pt);
                NativeMethods.SetCursorPos(pt.X, pt.Y);
                //Update our current absolute position
                NativeMethods.GetCursorPos(out _pos);
            }
        }
    }
}
